/*
XPTracker - Alternative Agile Tracking Tool
Copyright (C) 2006-2009  Stephen Starkey

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/
package org.xptracker.port;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.dom4j.tree.DefaultElement;
import org.xptracker.domain.*;
import org.xptracker.persistence.LastModifiedListener;
import org.xptracker.persistence.PersistenceManager;

import java.io.InputStream;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

@Singleton
public class BugzillaImporter {
  @SuppressWarnings({"UnusedDeclaration"})
  @Inject
  private Domain domain;

  private static final DateFormat DATE_NOSEC = new SimpleDateFormat("yyyy-MM-dd HH:mm");
  private static final DateFormat DATE_SEC = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

  private static final Map<String, State> BUG_STATES = makeBugStates();

  private static Map<String, State> makeBugStates() {
    Map<String, State> states = new HashMap<String, State>();
    states.put("UNCONFIRMED", State.dreaming);
    states.put("NEW", State.dreaming);
    states.put("REOPENED", State.dreaming);
    states.put("ASSIGNED", State.inProcess);
    states.put("VERIFIED", State.tested);
    states.put("SUSPENDED", State.blocked);
    return states;
  }

  public void importData(InputStream stream, PersistenceManager.UpdateReceiver receiver, User currentUser) {
    SAXReader reader = new SAXReader();
    Document document;
    try {
      document = reader.read(stream);
    } catch (DocumentException e) {
      throw new RuntimeException("Error reading document", e);
    }

    LastModifiedListener.enabled = false;
    try {
      //noinspection unchecked
      List<DefaultElement> nodes = (List<DefaultElement>) document.selectNodes("//bug");
      for (DefaultElement node : nodes) {
        Story s = new Story();

        String status = getValue(node, "bug_status");
        s.setState(convertState(status, getValue(node, "resolution")));
        if (s.getState() == null) {
          throw new RuntimeException("Unsupported bug status '" + status + "'.  Supported statuses: " +
              BUG_STATES.keySet());
        }
        s.setSummary(getValue(node, "short_desc"));
        s.setCreatedDate(date(DATE_NOSEC, getValue(node, "creation_ts")));
        s.setLastModified(date(DATE_SEC, getValue(node, "delta_ts")));
        s.setCreator(createOrLoadUser(receiver, node, "reporter"));
        if (s.getState() == State.inProcess) {
          s.changeOwner(createOrLoadUser(receiver, node, "assigned_to"));
        }
        s.setSeverity(convertSeverity(node));

        addTrackers(receiver, s, node);
        addCategories(receiver, s, node);

        receiver.insert(s);

        List<BlogEntry> blogEntries = convertBlogEntries(receiver, s, node);
        Collections.sort(blogEntries, new Comparator<BlogEntry>() {
          public int compare(BlogEntry first, BlogEntry second) {
            return first.getCreatedDate().compareTo(second.getCreatedDate());
          }
        });

        BlogEntry storyDescription = blogEntries.remove(0);
        s.setDescription(storyDescription.getContent());

        for (BlogEntry e : blogEntries) {
          receiver.insert(e);
        }

        BlogEntry importEntry = new BlogEntry(currentUser, s, "Imported from Bugzilla #" + getValue(node, "bug_id"));
        receiver.insert(importEntry);
        blogEntries.add(importEntry);

        s.setBlogEntries(blogEntries);
        receiver.update(s);
      }

      LogFactory.getLog(getClass()).info(currentUser.getUsername() + " imported " + nodes.size() +
          " stories from Bugzilla.");
    } finally {
      LastModifiedListener.enabled = true;
    }
  }

  private void addCategories(PersistenceManager.UpdateReceiver receiver, Story s, DefaultElement node) {
    addCategory(receiver, s, node, "classification");
    addCategory(receiver, s, node, "product");
    addCategory(receiver, s, node, "component");
    addCategory(receiver, s, node, "rep_platform");
    addCategory(receiver, s, node, "op_sys");

    String keywordsStr = getValue(node, "keywords");
    if (StringUtils.isNotBlank(keywordsStr)) {
      String[] keywords = keywordsStr.split(",");
      for (String keyword : keywords) {
        addCategory(keyword.trim(), receiver, s);
      }
    }
  }

  private void addCategory(PersistenceManager.UpdateReceiver receiver, Story s, DefaultElement node, String nodeName) {
    addCategory(getValue(node, nodeName), receiver, s);
  }

  private void addCategory(String value, PersistenceManager.UpdateReceiver receiver, Story s) {
    if (StringUtils.isNotBlank(value)) {
      StoryCategory category = domain.loadCategory(value);
      if (category == null) {
        category = new StoryCategory(value);
        receiver.insert(category);
      }
      s.getCategories().add(category);
    }
  }

  private void addTrackers(PersistenceManager.UpdateReceiver receiver, Story s, DefaultElement node) {
    for (DefaultElement e : ((List<DefaultElement>) node.elements("cc"))) {
      addUserToTrackers(createOrLoadUser(e.getText().trim(), receiver), s);
    }

    addUserToTrackers(createOrLoadUser(receiver, node, "qa_contact"), s);
  }

  private void addUserToTrackers(User user, Story s) {
    if (user != null) {
      s.getTrackers().add(user);
    }
  }

  private State convertState(String status, String resolution) {
    if ("RESOLVED".equals(status)) {
      return "FIXED".equals(resolution) ? State.developed : State.rejected;
    }

    if ("CLOSED".equals(status)) {
      return "FIXED".equals(resolution) ? State.released : State.rejected;
    }
    return BUG_STATES.get(status);
  }

  private List<BlogEntry> convertBlogEntries(PersistenceManager.UpdateReceiver receiver, Story s, DefaultElement node) {
    List<BlogEntry> entries = new ArrayList<BlogEntry>();
    //noinspection unchecked
    for (DefaultElement longDesc : ((List<DefaultElement>) node.elements("long_desc"))) {
      BlogEntry entry = new BlogEntry(createOrLoadUser(receiver, longDesc, "who"), s, getValue(longDesc, "thetext"));
      entry.setCreatedDate(date(DATE_SEC, getValue(longDesc, "bug_when")));
      entries.add(entry);
    }
    return entries;
  }

  private Severity convertSeverity(DefaultElement node) {
    String priority = getValue(node, "priority");
    int priorityIndex = "--".equals(priority) ? 3 : new Integer(priority.substring(1));
    return Severity.values()[5 - priorityIndex];
  }

  private User createOrLoadUser(PersistenceManager.UpdateReceiver receiver, DefaultElement node, String nodeName) {
    return createOrLoadUser(getValue(node, nodeName), receiver);
  }

  private User createOrLoadUser(String address, PersistenceManager.UpdateReceiver receiver) {
    if (address != null) {
      String username = address.split("@")[0];
      User u = domain.loadUser(username);
      if (u == null) {
        u = new User(username);
        u.setEmail(address);
        u.addRole(Role.editor);
        receiver.insert(u);
      }
      return u;
    }
    return null;
  }

  private Date date(DateFormat fmt, String value) {
    try {
      return fmt.parse(value);
    } catch (ParseException e) {
      throw new RuntimeException(e);
    }
  }

  private String getValue(DefaultElement node, String nodeName) {
    Element element = node.element(nodeName);
    return element == null ? null : element.getText().trim();
  }
}
